using System;
using System.Linq;
using Basmus.Conel;

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Ninject;

namespace Basmus.Tests
{
    [TestClass]
    public class NullableTypesBindingBlackBoxTest
    {
        private IBinder _binder;

        private Binding _binding;

        [TestInitialize]
        public void Initialize()
        {
            _binder = new StandardKernel(new TestNinjectModule()).Get<IBinder>();
        }

        #region simple tests

        [TestMethod]
        public void GettingIntElementsForContainerWithValueTest()
        {
            //Arrange
            var containerType = typeof(int?);
            var containerObjs = new int?[] { 0, 1, 4, 6, -1, -100000323 };

            //Act & Assert
            _binding = _binder.Bind(containerType);
            Assert.IsNotNull(_binding);
            Assert.AreEqual(_binding.ElementType, typeof(int?));

            //Act & Assert
            foreach (var containerObj in containerObjs)
            {
                Assert.IsTrue(containerObj.HasValue);
                var elements = _binding.GetElements(containerObj);
                Assert.IsTrue(elements.Length == 1);
                Assert.IsTrue(elements[0] is int);
                Assert.IsTrue((int)elements[0] == containerObj.Value);                
            }
        }

        [TestMethod]
        public void GettingIntElementsForContainerWithoutValueTest()
        {
            //Arrange
            var containerType = typeof(int?);
            var container = new int?();

            //Act & Assert
            _binding = _binder.Bind(containerType);
            Assert.IsNotNull(_binding);
            Assert.AreEqual(_binding.ElementType, typeof(int?));

            //Act & Assert
            Assert.IsFalse(container.HasValue);
            var elements = _binding.GetElements(container);
            Assert.IsTrue(elements.Length == 1);
            Assert.IsTrue(elements[0] == null);

        }

        [TestMethod]
        public void GettingIntElementsForNullContainerTest()
        {
            //Arrange
            var containerType = typeof(int?);

            //Act & Assert
            _binding = _binder.Bind(containerType);            
            Assert.IsNotNull(_binding);
            Assert.AreEqual(_binding.ElementType, typeof(int?));

            //Act
            var elements = _binding.GetElements(null);
            Assert.IsTrue(elements.Length == 1);
        }

        [TestMethod]
        public void GettingIntContainerTest()
        {
            //Arrange
            var containerType = typeof(int?);
            var elementSets = new[]
                                 {
                                     new object[] {0}, 
                                     new object[] {1}, 
                                     new object[] {6}, 
                                     new object[] {-1, 5, 7},
                                     new object[] {-100000323}
                                 };
            var containers = new int?[]
                           {
                               0, 
                               1, 
                               6, 
                               -1, 
                               -100000323
                           };

            //Act & Assert
            _binding = _binder.Bind(containerType);
            Assert.IsNotNull(_binding);
            Assert.AreEqual(_binding.ElementType, typeof(int?));

            //Act & Assert
            for (int i = 0; i < containers.Length; ++i)
            {
                var containerObj = _binding.GetContainer(elementSets[i]);

                Assert.IsInstanceOfType(containerObj, typeof(int?));
                var container = containerObj as int?;
                Assert.IsTrue(container.HasValue && container.Value == containers[i].Value);
            }
        }

        [TestMethod]
        public void GettingIntContainerForEmptyElementsListTest()
        {
            //Arrange
            var containerType = typeof(int?);
            var elements = new object[0];

            //Act & Assert
            _binding = _binder.Bind(containerType);
            Assert.IsNotNull(_binding);
            Assert.AreEqual(_binding.ElementType, typeof(int?));

            //Act & Assert
            var container = _binding.GetContainer(elements);
            Assert.IsNull(container);
        }

        [TestMethod]
        public void GettingIntContainerForNullElementsListTest()
        {
            //Arrange
            var containerType = typeof(int?);

            //Act & Assert
            _binding = _binder.Bind(containerType);
            Assert.IsNotNull(_binding);
            Assert.AreEqual(_binding.ElementType, typeof(int?));

            //Act & Assert
            var container = _binding.GetContainer(null);
            Assert.IsNull(container);
        }

        [TestMethod]
        public void GettingIntContainerForElementsListWithNullTest()
        {
            //Arrange
            var containerType = typeof(int?);
            var elementSets = new[]
                                 {
                                     new object[] {null}, 
                                     new object[] {null, 5, 7},
                                     new object[] {null, -100000323}
                                 };

            //Act & Assert
            _binding = _binder.Bind(containerType);
            Assert.IsNotNull(_binding);
            Assert.AreEqual(_binding.ElementType, typeof(int?));

            //Act & Assert
            foreach (var elementSet in elementSets)
            {
                var containerObj = _binding.GetContainer(elementSet);

                Assert.IsNull(containerObj);
            }
        }

        #endregion

        [TestMethod]
        public void IncompatibleTypesTest()
        {
            //Arrange
            var elementTypes = TypeHelper.ValueTypes();
            var containerTypes = elementTypes.Select(t => typeof (Nullable<>).MakeGenericType(t));

            //Act & Assert)
            foreach (var containerType in containerTypes)
            {
                var elementType = containerType.GetGenericArguments()[0];

                foreach (var incompatibleType in elementTypes.Except(new[] { elementType }))
                {
                    _binding = _binder.Bind(containerType, incompatibleType);
                    Assert.IsNull(_binding);
                }

                _binding = _binder.Bind(containerType, elementType);
                Assert.IsNotNull(_binding);
            }
        }

        public void GettingElementsForContainerWithValueTestHelper<TElement>() where TElement : struct 
        {
            //Arrange
            var containerObjs = ElementsHelper.GetElementsOf<TElement>()
                                              .Where(set => set != null)
                                              .SelectMany(set => set)
                                              .Select(e => new TElement?(e))
                                              .ToArray();

            //Act & Assert
            Assert.IsNotNull(_binding);
            Assert.AreEqual(_binding.ElementType, typeof(TElement));

            //Act & Assert
            foreach (var containerObj in containerObjs)
            {
                Assert.IsTrue(containerObj.HasValue);
                var elements = _binding.GetElements(containerObj);
                Assert.IsTrue(elements.Length == 1);
                Assert.IsInstanceOfType(elements[0], typeof(TElement));
                Assert.AreEqual((TElement)elements[0], containerObj.Value);
            }
        }

        public void GettingElementsForContainerWithoutValueTestHelper<TElement>() where TElement : struct 
        {
            //Arrange
            var container = new TElement?();

            //Act & Assert    
            Assert.IsNotNull(_binding);
            Assert.AreEqual(_binding.ElementType, typeof(TElement));

            //Act & Assert
            Assert.IsFalse(container.HasValue);
            var elements = _binding.GetElements(container);
            Assert.IsNotNull(elements);
            Assert.IsTrue(elements.Length == 0);

        }

        public void GettingElementsForNullContainerTestHelper<TElement>() where TElement : struct 
        {
            //Act & Assert
            Assert.IsNotNull(_binding);
            Assert.AreEqual(_binding.ElementType, typeof(TElement));

            //Act
            var elements = _binding.GetElements(null);
            Assert.IsNotNull(elements);
            Assert.IsTrue(elements.Length == 0);
        }

        public void GettingContainerTestHelper<TElement>() where TElement : struct 
        {
            //Arrange
            var sets = ElementsHelper.GetElementsOf<TElement>().Where(set => set != null).ToArray();
                                              

            var elementSets = sets.Select(set => set.Cast<object>().ToArray()).ToArray();
            var containers = sets.Where(set => set.Any())
                                 .Select(set => set.Select(e => new TElement?(e)).First())
                                 .ToArray();

            //Act & Assert
            Assert.IsNotNull(_binding);
            Assert.AreEqual(_binding.ElementType, typeof(TElement));

            //Act & Assert
            for (int i = 0; i < containers.Length; ++i)
            {
                var containerObj = _binding.GetContainer(elementSets[i]);
                Assert.IsNotNull(containerObj);
                Assert.IsInstanceOfType(containerObj, typeof(TElement?));
                var container = containerObj as TElement?;
                Assert.IsTrue(container.HasValue);
                Assert.AreEqual(container.Value, containers[i].Value);
            }
        }

        public void GettingContainerForEmptyElementsListTestHelper<TElement>() where TElement : struct
        {
            //Arrange
            var elements = new object[0];

            //Act & Assert      
            Assert.IsNotNull(_binding);
            Assert.AreEqual(_binding.ElementType, typeof(TElement));

            //Act & Assert
            var container = _binding.GetContainer(elements);
            Assert.IsNull(container);
        }

        public void GettingContainerForNullElementsListTestHelper<TElement>() where TElement : struct
        {

            //Act & Assert
            Assert.IsNotNull(_binding);
            Assert.AreEqual(_binding.ElementType, typeof(TElement));

            //Act & Assert
            var container = _binding.GetContainer(null);
            Assert.IsNull(container);
        }

        public void GettingContainerForElementsListWithFirstNullTestHelper<TElement>() where TElement : struct
        {
            //Arrange
            var masks = ElementsHelper.GetElementsOf<bool>().Where(set => set != null);
            var elementSets = ElementsHelper.GetElementsOf<TElement>()
                                            .Where(set => set != null)
                                            .SelectMany(set => masks.Select(m => set.Select((e, i) => i < m.Count() && m.ElementAt(i) ? null : (object)e)))
                                            .Select(set => new object[] { null }.Concat(set).ToArray())
                                            .ToArray();

            //Act & Assert
            Assert.IsNotNull(_binding);
            Assert.AreEqual(_binding.ElementType, typeof(TElement));

            //Act & Assert
            foreach (var elementSet in elementSets)
            {
                var containerObj = _binding.GetContainer(elementSet);
                Assert.IsNull(containerObj);
            }
        }

        //[TestMethod]
        //public void NullableContainerTest()
        //{
        //    //Arrange
        //    var tests = new[]
        //                    {
        //                        "GettingElementsForContainerWithValueTestHelper",
        //                        "GettingElementsForContainerWithoutValueTestHelper",
        //                        "GettingElementsForNullContainerTestHelper",
        //                        "GettingContainerTestHelper",
        //                        "GettingContainerForEmptyElementsListTestHelper",
        //                        "GettingContainerForNullElementsListTestHelper",
        //                        "GettingContainerForElementsListWithFirstNullTestHelper"
        //                    };
        //    var elementTypes = TypeHelper.ValueTypes();

        //    //Act & Assert
        //    foreach (var elementType in elementTypes)
        //    {
        //        var containerType = typeof(Nullable<>).MakeGenericType(elementType);

        //        foreach (var test in tests)
        //        {
        //            _binding = _binderService.Bind(containerType);
        //            this.Invoke(test, elementType);

        //            _binding = _binderService.Bind(containerType, elementType);
        //            this.Invoke(test, elementType);
        //        }
        //    }
        //}
    }
}